/**
 * Copyright (C) 2012 Dmitriy Kukharev
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ru.nsu.weather;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedMap;

import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
import org.achartengine.util.MathHelper;

import ru.nsu.weather.AppErrorDialog.DialogListener;
import ru.nsu.weather.data.Weather;
import ru.nsu.weather.net.DownloadWeatherTask;
import ru.nsu.weather.net.DownloadWeatherTask.Parameters;
import ru.nsu.weather.net.TemperatureListener;
import android.content.Context;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * Main application activity class. 
 * @author Dmitriy Kukharev
 */
public class MainActivity extends FragmentActivity implements TemperatureListener, OnTouchListener {
	// log tag
	private static final String CLASS_TAG = MainActivity.class.getName();

	// weather chart dataset and renderer
	private XYMultipleSeriesDataset weatherDataset;
	private XYMultipleSeriesRenderer weatherChartRenderer;
	
	// weather chart view
	private GraphicalView chartView;
	
	private ProgressBar progressBar;
	
	// max and min values for loaded chart
	private double xMin = Double.NaN;
	private double xMax = Double.NaN;
	
	// last values for min and max of Y before Y-axis is updated.
	private double prevXMin = Double.NaN;
	private double prevXMax = Double.NaN;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	Log.d(CLASS_TAG, "onCreated executed");
        
    	super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
    	ViewGroup mainViewGroup = (ViewGroup) findViewById(R.id.main_view);
    	progressBar = (ProgressBar) findViewById(R.id.progress_bar);
    	progressBar.setVisibility(ProgressBar.GONE);
    	
    	weatherDataset = createDataset();
    	weatherChartRenderer = createRenderer();
    	chartView = ChartFactory.getCubeLineChartView(
    			this, weatherDataset, weatherChartRenderer, 0.33f);
    	chartView.setOnTouchListener(this);
    	mainViewGroup.addView(chartView);
    	Log.d(CLASS_TAG, "Weather chart created");
    }
    
    @Override
    protected void onStart() {
    	Log.d(CLASS_TAG, "onStart executed");
    	super.onStart();
    	
    	if (checkConnection()) {
        	Log.d(CLASS_TAG, "Network available");
        } else {
        	Log.d(CLASS_TAG, "Network is not available");
        	displayError(getResources().getString(R.string.connection_error));
        	return;
        }

    	loadWeather(new DownloadWeatherTask.Parameters());
    }
    
    @Override
    protected void onResume() {
    	Log.d(CLASS_TAG, "onResume executed");
    	super.onResume();
    }
    
    @Override
    protected void onPause() {
    	Log.d(CLASS_TAG, "onPause executed");
    	super.onPause();
    }
    
    @Override
    protected void onStop() {
    	Log.d(CLASS_TAG, "onStop executed");
    	super.onStop();
    }
    
    @Override
    protected void onRestart() {
    	Log.d(CLASS_TAG, "onRestart executed");
    	super.onRestart();
    }
    
    @Override
    protected void onDestroy() {
    	Log.d(CLASS_TAG, "onDestroy executed");
    	super.onDestroy();
    }
    
    // start loading new weather data
    private void loadWeather(DownloadWeatherTask.Parameters params) {
    	progressBar.setVisibility(ProgressBar.VISIBLE);
    	new DownloadWeatherTask(this).execute(params);
    }
    
    // Displays weather on chart
    private void displayWeather(Weather weather) {
    	progressBar.setVisibility(ProgressBar.GONE);
    	TextView textView = (TextView) findViewById(R.id.currentTempMessage);
    	
    	// update current temperature
    	String template = getResources().getString(R.string.current_temp);
    	textView.setText(template.replace("{temp}", String.valueOf(weather.getCurrent())));
    	
    	// update dataset with the new weather snapshot
    	updateDataset(weather);
    	// update renderer values
    	updateYAxis();
    	// repaint charts
    	chartView.repaint();
    }
    
    // Displays caught error in AppErrorDialog
    private void displayError(Throwable error) {
    	progressBar.setVisibility(ProgressBar.GONE);
    	Log.e(CLASS_TAG, "An error occured while measuring temperature", error);
    	displayError(getResources().getString(R.string.network_error));
    }

    // Displays error message in AppErrorDialog
    private void displayError(String message) {
    	DialogFragment fragment = 
    			AppErrorDialog.create(
    					new DialogListener() {
							@Override
							public void onPositiveButtonClick(AppErrorDialog dialog) {
								finish();
							}
						}, message);
    	fragment.show(getSupportFragmentManager(), AppErrorDialog.class.getName());
    }
    
    // Checks coonnection
    private boolean checkConnection() {
    	 ConnectivityManager connMgr = (ConnectivityManager) 
                 getSystemService(Context.CONNECTIVITY_SERVICE);
         NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
         return networkInfo != null && networkInfo.isConnected();
    }

    // Updates dataset with the new weather snapshot
    private void updateDataset(Weather weather) {
    	if (weatherDataset == null)
    		return;
        
    	XYSeries series = weatherDataset.getSeriesAt(0);
    	
    	List<Weather.Point> points = weather.filter(10).getPoints();
        int n = points.size();
        for (int i = 0; i < n; i++) {
        	Weather.Point point = points.get(i);
        	series.add(point.getTimestamp(), point.getTemp());
        }
    }
    
    // Updates y axis and sets new min and max values after graph was moved.
    private void updateYAxis() {
    	double xMin = weatherChartRenderer.getXAxisMin();
		double xMax = weatherChartRenderer.getXAxisMax();
		
		// it is enough to check only xMin
		boolean initialized = !Double.isInfinite(xMin) && !Double.isNaN(xMin) && 
				xMin != MathHelper.NULL_VALUE;
		
		if (!initialized)
			return;
    	
    	XYSeries series = weatherDataset.getSeriesAt(0);
		SortedMap<Double, Double> range = series.getRange(xMin, xMax, 0);
		List<Double> values = new ArrayList<Double>(range.values());
		Collections.sort(values);
		Double yMin = values.get(0);
		Double yMax = values.get(values.size() - 1);
		
		weatherChartRenderer.setYAxisMin(yMin);
		weatherChartRenderer.setYAxisMax(yMax);
		
		Log.d(CLASS_TAG, "values.length=" + values.size() + ", min temp=" + yMin + ", max temp=" + yMax);
    }
    
    // Creates empty dataset
    private XYMultipleSeriesDataset createDataset() {
    	XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();
        dataset.addSeries(new XYSeries("temperature"));
        return dataset;
    }
    
    // Creates weather chart renderer
    private XYMultipleSeriesRenderer createRenderer() {
    	XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
//    	renderer.setApplyBackgroundColor(false);
//    	renderer.setZoom
    	renderer.setPanEnabled(true, false);
        renderer.setAxisTitleTextSize(16);
        renderer.setChartTitleTextSize(20);
        renderer.setLabelsTextSize(15);
        renderer.setLegendTextSize(15);
        renderer.setPointSize(5f);
        renderer.setMargins(new int[] {20, 30, 15, 0});
        renderer.setApplyBackgroundColor(false);
        renderer.setShowGrid(true);
        renderer.setGridColor(Color.GREEN);
        renderer.setMarginsColor(Color.RED);
        
        long epoch = System.currentTimeMillis() / 1000;
        double[] limits = new double[] {
        		Double.NEGATIVE_INFINITY,
        		epoch,
        		-20,
        		20
        };
        
        renderer.setPanLimits(limits);
        
//        renderer.setYLabelsAlign(Align.LEFT)
        XYSeriesRenderer r = new XYSeriesRenderer();
        r.setColor(Color.RED);
//        r.setPointStyle(PointStyle.);
//        r.setFillBelowLine(true);
//        r.setFillBelowLineColor(Color.WHITE);
        r.setFillPoints(false);
        renderer.addSeriesRenderer(r);
//        r = new XYSeriesRenderer();
//        r.setPointStyle(PointStyle.CIRCLE);
//        r.setColor(Color.GREEN);
//        r.setFillPoints(true);
//        renderer.addSeriesRenderer(r);
//        renderer.setAxesColor(Color.DKGRAY);
//        renderer.setLabelsColor(Color.LTGRAY);
        return renderer;
    }
    
    @Override
    public void weatherChanged(Weather weather) {
    	displayWeather(weather);
    }
    
    @Override
    public void measurementFailed(Throwable e) {
    	displayError(e);
    }
    
    @Override
    public boolean onTouch(View v, MotionEvent event) {
    	double newXMin = weatherChartRenderer.getXAxisMin();
		double newXMax = weatherChartRenderer.getXAxisMax();
		
		// it is enough to check only xMin
		boolean initialized = !Double.isInfinite(newXMin) && !Double.isNaN(newXMin) && 
				newXMin != MathHelper.NULL_VALUE;
		
		if (!initialized)
			return false;
		
		// initialize values for the first time
    	if (Double.isNaN(xMin) || Double.isNaN(xMax)) {
    		xMin = newXMin;
    		xMax = newXMax;
    		return false;
    	}
    	
    	if (event.getAction() == MotionEvent.ACTION_MOVE) {
    		// if previous values were not initialized then initialized them and
    		// exit the method
    		if (Double.isNaN(prevXMin) || Double.isNaN(prevXMax)) {
    			prevXMin = newXMin;
    			prevXMax = newXMax;
    			return false;
    		}
    			
    		// update y-axis each time screen is moved left or right by one tenth of its width
    		double width = newXMax - newXMin;
    		if (Math.abs(prevXMax - newXMax) > width / 10) {
    			updateYAxis();
    			prevXMax = newXMax;
    			prevXMin = newXMin;
    		}
    	} else if (event.getAction() == MotionEvent.ACTION_UP) {
    		try {
        		long start = 0L, stop = 0L;
        		if (newXMin < xMin) {
        			// scrolled to the left
        			start = Double.valueOf(newXMin).longValue();
        			stop = Double.valueOf(xMin).longValue();
        			Log.d(CLASS_TAG, "new requested weather range=[" + start + ", " + stop + "]");
        			loadWeather(new Parameters(start, stop));
        			xMin = newXMin;
        		} else if (newXMax > xMax) {
        			// scrolled to the right
        			start = Double.valueOf(xMax).longValue();
        			stop = Double.valueOf(newXMax).longValue();
        			Log.d(CLASS_TAG, "new requested weather range=[" + start + ", " + stop + "]");
        			loadWeather(new Parameters(start, stop));
        			xMax = newXMax;
        		}
    		} catch (IllegalArgumentException e) {
    			Log.d(CLASS_TAG, e.getMessage());
    			// wrong parameters -> proceed until next event
    			return false;
    		}
    	}
    	
    	// always return false in order to internal chart method are able to executed
    	return false;
    }
}
